/*
Copyright 2010-2011 CumulusCloud
http://cumuluscloud.cc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cumulusCloud.common;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Map;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cumulusCloud.common.objects.CloudUser;

@SuppressWarnings("serial")
public class PHPServlet extends HttpServlet {
	public static String phpcgi = null;
	
	private String cleanPath(String path) {
		path = path.replace('\\', '/');
		path = path.replaceAll("//", "/");
		path = path.replace('/', File.separatorChar);
		
		return path;
	}
	
	private Process createProcess(HttpServletRequest req) throws IOException {
		// Get the working directory
		String curDir = cleanPath(InformationPath.getCloudAppsInstallPath() + req.getContextPath());
		
		// Prepare and start the process
		ProcessBuilder pb = new ProcessBuilder(phpcgi);
		pb.directory(new File(curDir));
		setHeader(req, pb.environment());
		return pb.start();
	}
	
	@Override
	public void doDelete(HttpServletRequest req, HttpServletResponse res) {
		doGet(req, res);
	}
	
	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse res) {
		try {
			// Check for php CGI
			if (!findCGI()) {
				sendError(res);
				return;
			}
			
			// Create the process
			Process p = createProcess(req);
			
			// Send the response to the client
			sendResponse(p, res);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
	
	@Override
	public void doHead(HttpServletRequest req, HttpServletResponse res) {
		doGet(req, res);
	}

	@Override
	public void doPost(HttpServletRequest req, HttpServletResponse res) {
		try {
			// Check for php CGI
			if (!findCGI()) {
				sendError(res);
				return;
			}
			
			// Create the process
			Process p = createProcess(req);
			
			// Send the content to the server
			sendContent(req.getInputStream(), p.getOutputStream(), req.getContentLength());
			
			// Send the response to the client
			sendResponse(p, res);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	@Override
	public void doPut(HttpServletRequest req, HttpServletResponse res) {
		doPost(req, res);
	}
	
	private boolean findCGI() {
		if (phpcgi != null) return true;
		
		// List of the possibilities
		String[] possible = {
				"php-cgi",
				"php-cgi.exe",
				cleanPath(InformationPath.getCloudAppsInstallPath() + "php-windows/WEB-INF/php/php-cgi.exe")
		};
		
		// Test them out
		for (String next : possible) {
			if (testExecution(next)) {
				phpcgi = next;
				break;
			}
		}
		
		return phpcgi != null;
	}
	
	private void putIfNotNull(Map<String, String> map, String name, Object value) {
		if (value != null) {
			map.put(name, value.toString());
		}
	}

	private void sendContent(ServletInputStream in, OutputStream out, int length) throws IOException {
		// Copy the content
		int len;
		byte b[] = new byte[1000];
		while ( (length > 0) && ((len = in.read(b)) != -1) ) {
			out.write(b, 0, len);
			length -= len;
		}
		out.close();
	}
	
	private void sendError(HttpServletResponse res) throws IOException {
		PrintWriter pw = res.getWriter();
		pw.println("PHP-CGI was not found. If you are on Windows, install the webapp: php-windows.");
	}
	
	private void sendResponse(Process p, HttpServletResponse res) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
		PrintWriter pw = new PrintWriter(res.getOutputStream());
		
		// Read the headers
		String line;
		while ( !(line = reader.readLine()).isEmpty() ) {
			String parts[] = line.split(":", 2);
			if (parts[0].equalsIgnoreCase("status")) {
				parts = parts[1].split(" ");
				res.setStatus(Integer.parseInt(parts[1]));
				continue;
			}
			res.setHeader(parts[0], parts[1]);
		}
		
		// Copy the content
		int len;
		char c[] = new char[1000];
		while ( (len = reader.read(c)) != -1) {
			pw.write(c, 0, len);
		}
		
		pw.flush();
	}
	
	private void setHeader(HttpServletRequest req, Map<String, String> env) {
		String script = cleanPath(req.getRequestURI().substring(req.getContextPath().length() + 1));
		String curDir = cleanPath(InformationPath.getCloudAppsInstallPath() + req.getContextPath());
		String scriptRelative = req.getRequestURI();
		if (scriptRelative.endsWith("/")) {
			scriptRelative += "index.php";
		}
		
		putIfNotNull(env, "AUTH_TYPE", req.getAuthType());
		
		if (req.getMethod().endsWith("POST")) {
			putIfNotNull(env, "CONTENT_LENGTH", req.getContentLength());
		}
		
		putIfNotNull(env, "CONTENT_TYPE", req.getContentType());
		putIfNotNull(env, "DOCUMENT_ROOT", cleanPath(InformationPath.getCloudAppsInstallPath()));
		
		// HTTP headers
		try {
			@SuppressWarnings("unchecked")
			Enumeration<String> names = req.getHeaderNames();
			while (names.hasMoreElements()) {
				String name = names.nextElement();
				String value = req.getHeader(name); 
				
				name = "HTTP_" + name.toUpperCase().replace('-', '_');
				
				putIfNotNull(env, name, value);
			}
		} catch (Exception e) {}
		
		putIfNotNull(env, "PATH_INFO", req.getPathInfo());
		putIfNotNull(env, "PATH_TRANSLATED", req.getPathTranslated());
		putIfNotNull(env, "PHP_SELF", scriptRelative);
		putIfNotNull(env, "QUERY_STRING", req.getQueryString());
		putIfNotNull(env, "REDIRECT_STATUS", "200");
		putIfNotNull(env, "REMOTE_ADDR", req.getRemoteAddr());
		putIfNotNull(env, "REMOTE_HOST", req.getRemoteHost());
		putIfNotNull(env, "REMOTE_PORT", req.getRemotePort());
		putIfNotNull(env, "REMOTE_USER", req.getRemoteUser());
		putIfNotNull(env, "REQUEST_METHOD", req.getMethod());
		putIfNotNull(env, "REQUEST_URI", req.getRequestURI());
		String script_filename = cleanPath(curDir + File.separatorChar + script);
		if (new File(script_filename).isDirectory()) {
			script_filename += "index.php";
		}
		putIfNotNull(env, "SCRIPT_FILENAME", script_filename);
		putIfNotNull(env, "SCRIPT_NAME", scriptRelative);
		putIfNotNull(env, "SERVER_NAME", req.getServerName());
		putIfNotNull(env, "SERVER_PORT", req.getServerPort());
		putIfNotNull(env, "SERVER_PROTOCOL", req.getProtocol());
		putIfNotNull(env, "SERVER_SOFTWARE", getServletContext().getServerInfo());
		
		// Cloud related information
		putIfNotNull(env, "CC_CORE_VERSION", InformationCore.getCoreVersion());
		
		putIfNotNull(env, "CC_INTERNET_BASE_URL", InformationServer.getInternetBaseURL());
		putIfNotNull(env, "CC_INTERNET_IP", InformationServer.getInternetIP());
		
		putIfNotNull(env, "CC_PATH_APPS_DATA", cleanPath(InformationPath.getCloudAppsDataPath("")));
		putIfNotNull(env, "CC_PATH_APPS_INSTALL", InformationPath.getCloudAppsInstallPath());
		putIfNotNull(env, "CC_PATH_CATALINA_CONFIG", InformationPath.getCatalinaConfigPath());
		putIfNotNull(env, "CC_PATH_GLOBAL_CONFIG", InformationPath.getCloudGlobalConfigPath());
		
		String token = ManageSession.getCurrentUserToken(req);
		if (token == null) return;
		CloudUser cu = ManageSession.getSessionUser(token);
		if (cu == null) return;
		putIfNotNull(env, "CC_USER_ISADMIN", cu.isAdmin);
		putIfNotNull(env, "CC_USER_LANGUAGE", cu.language);
		putIfNotNull(env, "CC_USER_NAME", cu.name);
	}

	private boolean testExecution(String application) {
		try {
			ProcessBuilder pb = new ProcessBuilder(application, "-v");
			pb.start();
		} catch (Exception e) {
			return false;
		}
		
		return true;
	}
}
